El concepto de Execution Context es algo normalmente reservado para solo los desarrolladores avanzados de Javascript. No es común que se considere un tema digno de enseñarse al principio.
Sin embargo entenderlo es la vía a hacerte la vida mas fácil cuando estes debuggeando o aprendiendo otros conceptos del lenguaje, como this
o el Call Stack.
En esta primera parte veremos como se comporta el Execution Context principal de cualquier programa de JS.
¿Qué es?
Primero lo primero. ¿Qué es un Execution Context?
Un execution context se traduce literalmente a “contexto de ejecución”. Como el nombre lo dice, se refiere precisamente a la parte desde donde se esta ejecutando el código.
Hay 2 tipos de execution contexts:
- Global Execution Context
- Function Execution Context
El Global Execution Context (GEC) es el contexto que se crea a nivel global de tu código. Como te puedes imaginar es el contexto de ejecución principal. Todo lo que escribes corre en el GEC.
El Function Execution Context (FEC) es el contexto de ejecución que se crea dentro de una función. Cada función que mandes a llamar (ojo: que mandes a llamar, no que definas) creará un FEC durante la ejecución.
*Si ya dominas GEC puedes empezar a ver FEC aquí.
¿Cómo se crea?
Tu no eres el encargado de crear ningún Execution Context. Esto podrá parecer obvio pero no quiero que te me confundas.
El motor de Javascript es el encargado de realizar este proceso al momento de leer tu código. Y para lograrlo se hace a través de 2 fases:
- Fase De Creación – Se preparan los objeto, los espacios en memoria y las referencias
- Fase de Ejecución – Se asignan los valores de las variables
¿Cómo se conforma?
*Hay varios objetos que componen un Execution Context, cada uno para un propósito diferente. No te agobies con las definciones. Es importante que te familiarices con los términos pero más adelante veremos unos ejemplos.
Execution Context
Los primeros objetos que se encuentran dentro de un Execution Context son los Lexical Environments.
Un lexical environment (ambiente léxico) es una estructura que contiene las referencias a los identificadores distintas propiedades. Es decir referencias a variables y funciones declaradas.
Solo hay 2 tipos de Lexical Environment y todos los Execution Contexts los tienen (ya sea los utilizen o no):
- Lexical Environment – Es el ambiente primario y guarda toda las funciones y todas las variables declaradas con
let
yconst
. - Variable Environment – Es el ambiente utilizado para variables declaradas con
var
.
Lexical Environment/Variable Environment
A su vez cada Lexical Environment contiene 2 objetos indispensables:
- Environment Record – Es donde se almacenan las referencias de las variables y funciones. Este se encuentra compuesto de otros Environment Records.
Es importante que tengas en cuenta que solo las variables declaradas convar
se alamacenan aquí durante esta fase. Variables decalradas conlet
yconst
se guardan en un lugar comúnmente referido por la comunidad como TDZ (Temporal Data Zone o Temporal Dead Zone). - OuterReference – Es la referencia al Execution Context anterior (también llamado Execution Context padre).
*Muchas veces encontrarás que se menciona que el Lexical Environment contiene tambíen a la referencia this
. Esto es técnicamente correcto, pero siendo más específicos esta referencia se encuentra dentro de un los Environment Records internos.
Environment Record
Por último cada Environment Record puede ser 1 de 3 tipos diferentes y cada uno contiene diferentes elementos:
El Declarative Environment Record guarda referencias a conceptos que se explican por sí mismos. Esto es lo que permite que se manden a llamar funciones y variables directamente.
Los 3 elementos que componen al Object Environment Record se refieren a:
- Variable Object (VO) – Es el objeto que almacena las variables declaradas con
var
y funciones declaradas. Este objeto es el mismo que el objeto global, por lo que solo esta presente dentro de un Global Execution Context. Las variables declradas conlet
yconst
se mantienen fuera del scope de este objeto por lo que solo se pueden llamar gracias al Declarative Environment Record. - Argument Object (AO) – Es el objeto especial arguments que contiene los parámetros que se pasaron a una función. Por esto mismo solo esta presente dentro de un Function Execution Context.
- this Binding– La keyword
this
referencia al objeto que creo el Execution Context.
Finalmente el Global Environment Record solo funciona como un objeto compuesto de los otros 2 Environment Records. A pesar de ser llamado global por lógica dentro de un FEC también puede existir un Environment Record que este compuesto de los otros 2.
El Ejemplo
Como ves son muchos conceptos a aprenderse. Pero es más fácil visualizarlos con un ejemplo. Observa el siguiente bloque de código:
let a = 10;
const b = 20;
var c = 30;
function hello() {
console.log(hola)
}
Tanto las variables como la función se encuentran a nivel global y el código se ejecuta en un navegador web.
¿Como funcionarían los execution contexts con sus lexical environments?
Fase de Creación
Javascript crea un Global Execution Context y crea 2 lexical environments para el mismo. Cada Lexical Environment a su vez ya contiene sus propios objetos reservados para sus Environment Records, incluyendo la Outer Reference y el this
binding.
Ambos Environment Records a su vez ya contienen 2 espacios para sus respectivos Declarative Environment Record y Object Environment Record.
1. El objeto inicial se vería de esta manera:
2. Posteriormente JS define el VO, el objeto global, dentro del Object Environment Record de ambos Lexical Environments.
Como en este ejemplo estamos corriendo el código en un navegador web ese objeto es window
.
3. Al leer el código JS encuentra que hay 1 variable declarada con var
. Esta variable la asigna al Variable Environment, dentro de su Declarative Environment Record
Esta variable se encuentra con valor undefined porque en la fase de creación JS solo esta haciendo el proceso de hoisting. Es decir solo esta guardando referencias para las variables.
4. Posteriormente JS encuentra que hay 1 variable declarada con let
, 1 con const
y una función. Por lo que las asigna al Lexical Environment primario…¿no?
Pero…un momento. Claramente hace falta a
y b
. ¿Porqué no estan en el Declarative Environment Record?
Recuerda que con let
y const
no se guardan las variables en el Environment Record y en su lugar se mantienen en el TDZ. Esto es porque estas variables fueron introducidas posteriormente en la versión de ES6 de Javascript con la intención de comportarse diferente a var
. Sin embargo se inicializan también con el valor de undefined
.
*Si quieres tener un entendimiento mas claro de las diferencias entre var
vs let
/const
puedes revisar este artículo.
5. JS procede a asignar el valor de la Outer Reference. En este caso estamos trabajando con el Global Execution Context y por lo tanto no hay un Execution Context anterior. Esto hace que el valor se quede sin asignar.
Por último JS asigna el valor de this
al objeto global, que es el que se uso para crear el GEC.
Al final de la fase de creación nos quedamos entonces con este Lexical Environment y Variable Environment:
Fase de Ejecución
En la fase de ejecución, el engine de Javascript lee el código una vez mas y conforme va encontrando líneas donde se asignan valores va actualizando las referencias correspondientes en los lexical environments.
Recuerda la función:
let a = 10;
const b = 20;
var c = 30;
function hello() {
console.log(hola)
}
Javascript ejecuta línea por línea:
Línea 1: Asigna 10 a la variable a
.
Se saca la variable de TDZ y se coloca su valor dentro del Lexical Environment.
Línea 2: Asigna b a la variable con b
.
Se saca la variable de TDZ y se coloca su valor dentro del Lexical Environment.
Línea 3: Se asigna el valor de 40 a la variable c
dentro del Variable Environment.
Línea 4-6: Contiene la definción de la función. Pero en este punto JS ya había guardado la referencia en la fase anterior.
¡Listo! Ese fue el ciclo de creación y ejecución de tu Global Execution Context.
Son muchos conceptos, pero una vez que los comprendes el proceso es bastante sencillo.
Pero el tema de los execution contexts no termina aquí. La siguiente pregunta a responder es: ¿Qué hay de los Function Execution Context?